package com.cysyz.mylib.dao.impl;

import com.cysyz.mylib.dao.BaseDao;
import com.cysyz.mylib.dao.Conds;
import com.cysyz.mylib.dao.PagingResult;
import com.cysyz.mylib.dao.engine.PersistenceEngine;
import com.cysyz.mylib.dao.engine.Query;
import com.cysyz.mylib.dao.engine.SQLQuery;
import com.cysyz.mylib.dao.type.Type;
import com.cysyz.mylib.dao.type.TypeResolver;

import java.io.Serializable;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * DAO实现类
 * @ClassName: BaseDaoImpl
 * @author 崔勇	cysyz1229@gmail.com
 * @Date 2014-4-24 下午3:15:43
 */
public class BaseDaoImpl implements BaseDao {
    private static int MAX_RESULT_PERPAGE = 100; //分页中每页显示页数的最大上限。
    private PersistenceEngine persistenceEngine;
    public BaseDaoImpl() {
        super();
    }

    protected BaseDaoImpl(PersistenceEngine persistenceEngine, TypeResolver typeResolver) {
        this.persistenceEngine = persistenceEngine;
        Type.setTypeResolver(typeResolver);
    }

    public void setPersistenceEngine(PersistenceEngine persistenceEngine) {
        this.persistenceEngine = persistenceEngine;
    }

    @Override
    public PersistenceEngine getPersistenceEngine() {
        return persistenceEngine;
    }

    @Override
    public int execute(String ql, List<Object> params) {
        Query query = persistenceEngine.createQuery(ql);
        setParams(params, query);
        return query.execute();
    }

    private void setParams(Collection<Object> paramRealValues, Query query) {
        if (paramRealValues != null) {
            int i = 0;
            for (Object param : paramRealValues) {
                if (param instanceof Collection) {
                    for (@SuppressWarnings("rawtypes")
                         Iterator iterator = ((Collection) param).iterator(); iterator.hasNext();) {
                        Object object = (Object) iterator.next();
                        query.setParameter(i++, object);
                    }
                } else {
                    query.setParameter(i ++, param);
                }
            }
        }
    }

    private void setParams(Map<String, Object> paramRealValues, Query query) {
        if (paramRealValues != null) {
            Set<String> keySet = paramRealValues.keySet();
            for (String namedParameter : keySet) {
                Object value = paramRealValues.get(namedParameter);
                if(value instanceof Collection<?>){
                    query.setParameterList(namedParameter, (Collection<?>)value);
                }else if(value instanceof Object[]){
                    query.setParameterList(namedParameter, (Object[])value);
                }else{
                    query.setParameter(namedParameter, value);
                }
            }
        }
    }

    @Override
    public int executeSql(String sql, List<Object> params) {

        SQLQuery sqlQuery = configSQLQuery(sql, params);

        return sqlQuery.execute();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> List<T> list (String sql, List<Object> params, Class<T> clazz) {
        SQLQuery sqlQuery = configSQLQuery(sql, params);
        return sqlQuery.addEntity(clazz).getResultList();
    }

    @Override
    public <T> List<T> list (String sql, List<Object> params, Class<T> clazz, Map<String, Type> scalars) {
        SQLQuery sqlQuery = configSQLQuery(sql, params);

        for (Map.Entry<String, Type> stringSQLTypeEntry : scalars.entrySet()) {
            String key = stringSQLTypeEntry.getKey();
            Type value = stringSQLTypeEntry.getValue();
            sqlQuery.addScalar(key, value.getRealType());
        }

        return sqlQuery.addEntity(clazz).getResultList();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> PagingResult<T> listPaging (String sql, List<Object> params, Class<T> clazz, int page, int maxresult) {
        maxresult = checkResultPerPage(maxresult);
        SQLQuery sqlQuery = configSQLQuery(sql, params);
        List<T> records = sqlQuery.addEntity(clazz).getResultList();
        long totalRecords = getCount(sql, params, clazz);
        if (page>=0 && maxresult>=0) sqlQuery.setFirstResult((page - 1) * maxresult).setMaxResults(maxresult);
        PagingResult<T> pagingResult = new PagingResult<T>(records, totalRecords , page, maxresult);
        return pagingResult;
    }

    @Override
    public <T> PagingResult<T> listPaging (String sql, List<Object> params, Class<T> clazz, Map<String, Type> scalars, int page, int maxresult) {
        maxresult = checkResultPerPage(maxresult);
        SQLQuery sqlQuery = configSQLQuery(sql, params);

        for (Map.Entry<String, Type> stringSQLTypeEntry : scalars.entrySet()) {
            String key = stringSQLTypeEntry.getKey();
            Type value = stringSQLTypeEntry.getValue();
            sqlQuery.addScalar(key, value.getRealType());
        }

        if (page>=0 && maxresult>=0) sqlQuery.setFirstResult((page - 1) * maxresult).setMaxResults(maxresult);

        List<T> records = sqlQuery.addEntity(clazz).getResultList();

        long totalRecords = getCount(sql, params, clazz);
        PagingResult<T> pagingResult = new PagingResult<T>(records, totalRecords , page, maxresult);
        return pagingResult;
    }

    private int checkResultPerPage(int maxresult) {
        if (maxresult > MAX_RESULT_PERPAGE) {
            maxresult = MAX_RESULT_PERPAGE;
        }
        return maxresult;
    }

    public long getCount(String sql, List<Object> params, Class clazz) {
        StringBuilder sqlBuilder = new StringBuilder("select count(*) as c from (");
        sqlBuilder.append(sql);
        sqlBuilder.append(") tmp_count_t");
        SQLQuery sqlQuery = configSQLQuery(sqlBuilder.toString(), params);
        sqlQuery.addScalar("c", Type.LONG.getRealType());
        return ((Long) sqlQuery.getSingleResult()).longValue();
    }

    @Override
    public Object getSingleResult(String ql, Map<String, Object> paramPlaceAndValues) {
        Query query = persistenceEngine.createQuery(ql);
        setParams(paramPlaceAndValues, query);
        return query.getSingleResult();
    }
    private SQLQuery configSQLQuery(String sql, List<Object> params) {
        SQLQuery sqlQuery = persistenceEngine.createSQLQuery(sql);
        int i = 0;
        for (Object param : params) {
            sqlQuery.setParameter(i ++, param);
        }
        return sqlQuery;
    }

    public void save(Object entity) {
        persistenceEngine.persist(entity);
    }

    public <T> void remove(Class<T> clazz, Serializable entityID) {
        persistenceEngine.remove(persistenceEngine.getReference(clazz, entityID));
    }

    @SuppressWarnings("rawtypes")
    public void removeByCollection(List list) {
        persistenceEngine.removeByCollection(list);
    }
    public void update(Object entity) {
        persistenceEngine.merge(entity);
    }

    public <T> T get(Class<T> clazz, Serializable entityID) {
        return persistenceEngine.find(clazz, entityID);
    }

    public <T> T load(Class<T> clazz, Serializable entityID) {
        return persistenceEngine.getReference(clazz, entityID);
    }

    public <T> long getCount(Class<T> clazz, Conds conds, Query queryForData) {
        SQLQuery sqlQuery = null;
        Pattern pattern = Pattern.compile("(\\s+?in\\s*?\\():([\\d\\w]+)\\)");
        Matcher matcher = pattern.matcher(queryForData.getQueryString());
        Collection<Object> paramValues = conds.getParamPlaceAndValues().values();
        if (matcher.find()) {
            String notReplacePart = matcher.group(1);
            String replacePart = matcher.group(2);
            @SuppressWarnings("unchecked")
            Collection<Object> inParmValues = (Collection<Object>) conds.getParamPlaceAndValues().get(replacePart);

            StringBuilder replacementBuilder = new StringBuilder(notReplacePart);
            for (int i = 0, j = inParmValues.size() ; i < j ; i++) {
                replacementBuilder.append("?,");
            }
            replacementBuilder.deleteCharAt(replacementBuilder.length() - 1);
            replacementBuilder.append(")");
            String hql = matcher.replaceAll(replacementBuilder.toString());
            sqlQuery = persistenceEngine.TranslateToCountSQL(hql);
        } else {
            sqlQuery = persistenceEngine.TranslateToCountSQL(queryForData);
        }

        if (conds != null) {
            setParams(paramValues, sqlQuery);
        }
        sqlQuery.addScalar("c", Type.LONG.getRealType());
        return ((Long) sqlQuery.getSingleResult()).longValue();
    }

    @Override
    public <T> long getCount(Class<T> clazz, Conds conds) {
        return this.getCount(clazz, conds, "");
    }

    @Override
    public <T> long getCount(Class<T> clazz, Conds conds, String queryPostfix) {
        String asTableName = clazz.getSimpleName().toLowerCase();
        StringBuilder sql = new StringBuilder("select count(");
        sql.append(asTableName);
        sql.append(") from ");
        sql.append(clazz.getSimpleName());
        sql.append(" as ");
        sql.append(asTableName);
        if (conds != null) {
            sql.append(conds.toString());
        }
        if (queryPostfix != null && !queryPostfix.trim().isEmpty()) {
            sql.append(" ");
            sql.append(queryPostfix);
        }
        Query query = persistenceEngine.createQuery(sql.toString());
        if (conds != null ) {
            setParams(conds, query);
        }
        Object singleResult = query.getSingleResult();
        return singleResult == null ? 0 : ((Long) singleResult).longValue();
    }

    @Override
    public <T> long getCount(Class<T> clazz) {
        return this.getCount(clazz, null);
    }

    public <T> List<T> list(Class<T> clazz, boolean isDistinct, String[] fieldNames, Conds conds ) {
        return this.list0(clazz, isDistinct, fieldNames, conds, null, null, false, -1, -1, false).getResultlist();
    }
    public <T> List<T> list(Class<T> clazz, boolean isDistinct, String[] fieldNames ) {
        return this.list0(clazz, isDistinct, fieldNames, null, null, null, false, -1, -1, false).getResultlist();
    }

    public <T> List<T> list(Class<T> clazz, String[] fieldNames, Conds conds ) {
        return this.list(clazz, false, fieldNames, conds);
    }

    public <T> List<T> list(Class<T> clazz, Conds conds ) {
        return this.list(clazz, false, null, conds);
    }

    public <T> List<T> list(Class<T> clazz) {
        return this.list(clazz, false, null, null);
    }

    @Override
    public <T> List<T> list(Class<T> clazz, String queryPrefix, String queryPostfix) {
        return this.list0(clazz, false, null, null, queryPrefix, queryPostfix, false, -1, -1, false).getResultlist();
    }

    @Override
    public <T> List<T> list(Class<T> clazz, String queryPrefix) {
        return this.list(clazz, queryPrefix, null);
    }

    public <T> List<T> listCacheAble(Class<T> clazz, boolean isDistinct,
                                     String[] fieldNames, Conds conds) {
        return this.list0(clazz, isDistinct, fieldNames, conds, null, null, false, -1, -1, true).getResultlist();
    }

    public <T> List<T> listCacheAble(Class<T> clazz, String[] fieldNames,
                                     Conds conds) {
        return this.listCacheAble(clazz, false, fieldNames, conds);
    }

    public <T> List<T> listCacheAble(Class<T> clazz, Conds conds) {
        return this.listCacheAble(clazz, false, null, conds);
    }

    public <T> List<T> listCacheAble(Class<T> clazz) {
        return this.listCacheAble(clazz, false, null, null);
    }

    public <T> PagingResult<T> listPaging(Class<T> clazz, int page, int maxresult ) {
        return this.listPaging(clazz, null, page, maxresult);
    }

    @Override
    public <T> PagingResult<T> listPaging(Class<T> clazz, String queryPrefix, String queryPostfix, int page, int maxresult) {
        return this.listPaging(clazz, null, queryPrefix, queryPostfix, page, maxresult );
    }

    @Override
    public <T> PagingResult<T> listPaging(Class<T> clazz, Conds conds,
                                          int page, int maxresult) {
        return this.listPaging(clazz, conds, null, null, page, maxresult);
    }

    public <T> PagingResult<T> listPaging( Class<T> clazz, Conds conds, String queryPrefix, String queryPostfix, int page, int maxresult) {
        maxresult = checkResultPerPage(maxresult);
        if (page <=0) {
            page = 1;
        }

        if (maxresult <=0) {
            maxresult = 10;
        }

        QueryResult<T> list = this.list0(clazz, false, null, conds, queryPrefix, queryPostfix, true, (page - 1) * maxresult, maxresult, false);
        PagingResult<T> pagingResult = new PagingResult<T>(list.getResultlist(), list.getTotalrecord(), page, maxresult);
        return pagingResult;
    }

    @SuppressWarnings("unchecked")
    private <T> QueryResult<T> list0(
            Class<T> clazz,
            boolean isDistinct,
            String[] fieldNames,
            Conds conds,
            String queryPrefix,
            String queryPostfix,
            boolean isPaging,
            int firstindex,
            int maxresult,
            boolean isCacheAble) {

        QueryResult<T> qr = new QueryResult<T>();

        Query query = generateQuery(clazz, isDistinct, fieldNames, conds, queryPrefix, queryPostfix);

        if (isCacheAble) {
            query.setCacheAble(true);
        }
        if (conds != null) {
            setParams(conds, query);
        }

        if (isPaging && firstindex>=0 && maxresult>=0) query.setFirstResult(firstindex).setMaxResults(maxresult);
        qr.setResultlist(query.getResultList());
        if (isPaging) {
			/*Pattern pattern = Pattern.compile("\\s+?in\\s+?");
			Matcher matcher = pattern.matcher(query.getQueryString());
			
			if (!matcher.find()) {*/
            if (conds != null )
                qr.setTotalrecord(getCount(clazz, conds, query));
            else
                qr.setTotalrecord(getCount(clazz, null, query));
			/*} else {
				if (conds != null )
					qr.setTotalrecord(getCount(clazz, conds, queryPostfix));
				else
					qr.setTotalrecord(getCount(clazz, null, queryPostfix));
			}*/
        }
        return qr;
    }

    private <T> Query generateQuery(Class<T> clazz, boolean isDistinct, String[] fieldNames, Conds conds, String queryPrefix, String queryPostfix) {

        StringBuilder ql = popluateQlPrefox(clazz, isDistinct, fieldNames, queryPrefix);

        if (conds != null) {
            ql.append(conds.toString());
        }
        if (queryPostfix != null && !queryPostfix.trim().isEmpty()) {
            ql.append(" ").append(queryPostfix);
        }
        Query query = persistenceEngine.createQuery(ql.toString());
        return query;
    }

    private <T> StringBuilder popluateQlPrefox(Class<T> clazz,
                                               boolean isDistinct, String[] fieldNames, String queryPrefix) {
        String tableName = clazz.getSimpleName();
        String asTableName = clazz.getSimpleName().toLowerCase();
        String classFullPath = clazz.getName();
        StringBuilder ql = null;
        StringBuilder selectFileds = new StringBuilder("");

        if (queryPrefix != null && !queryPrefix.trim().isEmpty()) {
            ql = new StringBuilder(queryPrefix);
        } else if (queryPrefix == null || queryPrefix.trim().isEmpty()){
            if (fieldNames != null) {
                for (String fieldName : fieldNames) {
                    if (fieldName == null || fieldName.trim().isEmpty()) {
                        continue;
                    }
                    selectFileds.append(asTableName).append(".").append(fieldName).append(",");
                }
            }

            if (!selectFileds.toString().isEmpty()) {
                selectFileds.deleteCharAt(selectFileds.length() - 1);

                ql = new StringBuilder("select ");
                if (isDistinct) {
                    ql.append("distinct ");
                }

                ql.append("new ").append(classFullPath).append("(").append(selectFileds.toString()).append(")").append(" from ");
            } else {
                ql = new StringBuilder(" from ");
            }
            ql.append(tableName).append(" as ").append(asTableName);
        }

        return ql;
    }

    private void setParams(Conds conds, Query query) {
        Map<String, Object> paramRealValues = conds.getParamPlaceAndValues();
        setParams(paramRealValues, query);
    }

    private static class QueryResult<T> {
        private List<T> resultlist;
        private long totalrecord;

        public List<T> getResultlist() {
            return resultlist;
        }

        public void setResultlist(List<T> resultlist) {
            this.resultlist = resultlist;
        }

        public long getTotalrecord() {
            return totalrecord;
        }

        public void setTotalrecord(long totalrecord) {
            this.totalrecord = totalrecord;
        }
    }

    public static void main(String[] args) {
        StringBuilder sqlBuilder = new StringBuilder(" SELECT  organization.`provincepk` AS provincepk,  organization.`citypk` AS citypk,  cf_province.`province` AS province,  cf_city.`city` AS city,  organization.`name` AS organization_name,  COUNT(organization.`pk`) AS login_times, 1399222800 AS login_date  FROM  `org_admin_login`,  `organization`,  cf_province,  cf_city  WHERE `org_admin_login`.`org_pk` = organization.`pk`   AND cf_province.`provincepk` = organization.`provincepk`  AND cf_city.`citypk` =organization.`citypk`  AND logintime >= ?  AND logintime <= ?  GROUP BY organization.`pk` ");
        StringBuilder replace = sqlBuilder.replace(0, sqlBuilder.indexOf("from"), " SELECT COUNT(*) ");
    }

}
